package com.doodle.common.utils;

import com.doodle.common.data.DataAdapter;

import java.util.HashMap;
import java.util.Map;

public class SlidingWindow implements DataAdapter.DataLoadedListener {
	
	public static final int REMOVE_STEP_DEFAULT = 0;
	public static final int REMOVE_STEP_FREE = 1;
	public static final int REMOVE_STEP_REMOVE_DATA = 2;
	public static final int REMOVE_STEP_UPDATE = 3;
	
	protected final int mCacheSize;
	protected final DataAdapter mDataAdapter;
	
	protected int mSize;
	protected int mVisibleStart = 0;
	protected int mVisibleEnd = 0;
	protected int mContentStart = 0;
	protected int mContentEnd = 0;
	protected int mVisibleRequestCount = 0;
	
	protected boolean mActive = true;
	
	protected Map<Long, Object> mRecycleBin = new HashMap<Long, Object>();
	
	public SlidingWindow(DataAdapter loader, int cacheSize) {
		mDataAdapter = loader;
		mCacheSize = cacheSize;
		mSize = loader.size();
	}
	
	public int size() {
		return mSize;
	}
	
	public void setVisibleWindow(int start, int end) {
		if (end <= 0 ) {
			return;
		}
		
        mVisibleStart = Math.min(start, mSize);
        mVisibleEnd = Math.min(end, mSize);
        
        int contentStart = (start + end) / 2 - mCacheSize / 2;
        contentStart = Utils.clamp(contentStart, 0, Math.max(0, mSize - mCacheSize));
        int contentEnd = Math.min(contentStart + mCacheSize, mSize);
        
        setContentWindow(contentStart, contentEnd);
        
        updateAllRequests();
    }
	
	private void setContentWindow(int start, int end) {
		if (start == mContentStart && end == mContentEnd) {
        	return;
        }
		
        if (start >= mContentEnd || mContentStart >= end) {
            for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
            	freeEntry(i);
            }
            for (int i = start; i < end; ++i) {
            	prepareEntry(i);
            }
        } else {
            for (int i = mContentStart; i < start; ++i) {
            	freeEntry(i);
            }
            for (int i = end, n = mContentEnd; i < n; ++i) {
            	freeEntry(i);
            }
            for (int i = start, n = mContentStart; i < n; ++i) {
            	prepareEntry(i);
            }
            for (int i = mContentEnd; i < end; ++i) {
            	prepareEntry(i);
            }
        }
        
        mContentStart = start;
        mContentEnd = end;
	}
	
	private void updateAllRequests() {
        mVisibleRequestCount = 0;
        for (int i = mVisibleStart, n = mVisibleEnd; i < n; ++i) {
        	mVisibleRequestCount += startEntry(i);
        }
        if (mVisibleRequestCount == 0) {
            requestInvisibleEntries();
        } else {
            cancelInvisibleEntries();
        }
    }

    private void requestInvisibleEntries() {
        int range = Math.max((mContentEnd - mVisibleEnd), (mVisibleStart - mContentStart));
        for (int i = 0 ;i < range; ++i) {
        	tryStartEntry(mVisibleEnd + i);
        	tryStartEntry(mVisibleStart - 1 - i);
        }
    }
    
    private void cancelInvisibleEntries() {
        int range = Math.max((mContentEnd - mVisibleEnd), (mVisibleStart - mContentStart));
        for (int i = 0 ;i < range; ++i) {
        	tryCancelEntry(mVisibleEnd + i);
        	tryCancelEntry(mVisibleStart - 1 - i);
        }
    }
    
    public void tryStartEntry(int index) {
    	if (index >= mContentStart && index < mContentEnd) {
            startEntry(index);
        }
    }
    
    public void tryCancelEntry(int index) {
    	if (index >= mContentStart && index < mContentEnd) {
            cancelEntry(index);
        }
    }
    
    public void tryFreeEntry(int index) {
    	if (index >= mContentStart && index < mContentEnd) {
            freeEntry(index);
        }
    }
    
    protected void prepareEntry(int index) {
    	mDataAdapter.prepare(index);
    }
    
	protected int startEntry(int index) {
		return mDataAdapter.start(index);
	}
	
	protected void cancelEntry(int index) {
		mDataAdapter.cancel(index);
	}
	
	protected void freeEntry(int index) {
		mDataAdapter.free(index);
	}
	
	public void onSizeChanged(int size) {
        if (mSize != size) {
            mSize = size;
            if (mContentEnd > mSize) {
            	for (int i = mSize; i < mContentEnd; ++i) {
            		freeEntry(i);
                }
            	mContentEnd = mSize;
            }
            if (mVisibleEnd > mSize) {
            	mVisibleEnd = mSize;
            }
        }
    }
	
	public void remove(int index) {
		remove(index, REMOVE_STEP_DEFAULT);
	}
	
	public void remove(int index, int step) {
		if (step == REMOVE_STEP_DEFAULT) {
			tryFreeEntry(index);
			mDataAdapter.remove(index);
			onEntryRemoved(index);
		} else if (step == REMOVE_STEP_FREE) {
			tryFreeEntry(index);
		} else if (step == REMOVE_STEP_REMOVE_DATA) {
			mDataAdapter.remove(index);
		} else if (step == REMOVE_STEP_UPDATE) {
			onEntryRemoved(index);
		}
	}
    
    protected void onEntryRemoved(int index) {
    	if (index < mVisibleStart) {
    		--mVisibleStart;
    	}
    	if (index < mVisibleEnd){
    		--mVisibleEnd;
    	}
    	if (index < mContentStart) {
    		--mContentStart;
    	}
    	if (index < mContentEnd){
    		--mContentEnd;
    	}
    	--mSize;
    	setVisibleWindow(mVisibleStart, mVisibleEnd);
    }
    
    public void pause() {
    	for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    		freeEntry(i);
        }
    	mActive = false;
    }
    
    public void resume() {
    	for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    		prepareEntry(i);
        }
    	updateAllRequests();
    	mActive = true;
    }
    
    public void clear() {
    	for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    		freeEntry(i);
        }
    	mSize = 0;
    	mVisibleStart = 0;
    	mVisibleEnd = 0;
    	mContentStart = 0;
    	mContentEnd = 0;
    	mVisibleRequestCount = 0;
    }

	@Override
	public void onDataLoaded(int index) {
		if (index >= mVisibleStart && index < mVisibleEnd) {
        	--mVisibleRequestCount;
            if (mVisibleRequestCount == 0 && mActive) {
            	requestInvisibleEntries();
            }
        }
	}
	
	public void beforeDataChanged() {
		mRecycleBin.clear();
		for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    		Object data = mDataAdapter.getDataObject(i);
    		if (data != null) {
    			mRecycleBin.put(mDataAdapter.getDataId(i), data);
    		}
    		mDataAdapter.setDataObject(i, null);
        }
		if (mSize > 0 && mContentStart > 0) {
			Object first = mDataAdapter.getDataObject(0);
			if (first != null) {
				mRecycleBin.put(mDataAdapter.getDataId(0), first);
			}
			mDataAdapter.setDataObject(0, null);
		}
	}
	
	public void afterDataChanged(int size) {
        mSize = size;
        if (mContentEnd > mSize) {
        	mContentEnd = mSize;
        }
        if (mVisibleEnd > mSize) {
        	mVisibleEnd = mSize;
        }
        for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
    		long id = mDataAdapter.getDataId(i);
    		Object data = mRecycleBin.get(id);
    		if (data != null) {
    			mDataAdapter.setDataObject(i, data);
    			mRecycleBin.remove(id);
    		}
        }
        if (mSize > 0 && mContentStart > 0) {
			long id = mDataAdapter.getDataId(0);
			Object first = mRecycleBin.get(id);
			if (first != null) {
				mDataAdapter.setDataObject(0, first);
				mRecycleBin.remove(id);
			}
        }
        for (Object data : mRecycleBin.values()) {
        	mDataAdapter.freeDataObject(data);
        }
        mRecycleBin.clear();
	}
}
